
#include <gtest/gtest.h>
#include "xacc.hpp"
#include "xacc_service.hpp"
#include "IRTransformation.hpp"
#include "GateFusion.hpp"
#include <unsupported/Eigen/KroneckerProduct>

TEST(GateMergingTester, checkSingleQubitSimple) 
{
    auto c = xacc::getService<xacc::Compiler>("xasm");
    // This is identity: H-X-H is equal to Z => Z-Z = I
    auto f = c->compile(R"(__qpu__ void test1(qbit q) {
        Z(q[0]);
        H(q[0]);
        X(q[0]);
        H(q[0]);
    })")->getComposites()[0];

    auto opt = xacc::getService<xacc::IRTransformation>("single-qubit-gate-merging");
    opt->apply(f, nullptr);
    // No instruction after optimization
    EXPECT_EQ(f->nInstructions(), 0);
}

TEST(GateMergingTester, checkSingleQubitStopAtTwoQubitGate) 
{
    auto c = xacc::getService<xacc::Compiler>("xasm");
    auto f = c->compile(R"(__qpu__ void test2(qbit q) {
        H(q[0]);
        X(q[0]);
        H(q[0]);
        CNOT(q[0], q[1]);
        H(q[0]);
    })")->getComposites()[0];

    auto opt = xacc::getService<xacc::IRTransformation>("single-qubit-gate-merging");
    opt->apply(f, nullptr);
    // Becomes: Z (Rz(pi)) - CNOT - H
    EXPECT_EQ(f->nInstructions(), 3);
    EXPECT_EQ(f->getInstruction(0)->name(), "Rz");
    EXPECT_NEAR(f->getInstruction(0)->getParameter(0).as<double>(), M_PI, 1e-6);
    EXPECT_EQ(f->getInstruction(1)->name(), "CNOT");
    EXPECT_EQ(f->getInstruction(2)->name(), "H");
}

TEST(GateMergingTester, checkMixing) 
{
    auto c = xacc::getService<xacc::Compiler>("xasm");
    auto f = c->compile(R"(__qpu__ void test2(qbit q) {
        H(q[1]);
        CNOT(q[1], q[0]);
        H(q[0]);
        H(q[1]);
        X(q[0]);
        // Not involved
        CNOT(q[2], q[3]);
        X(q[1]);
        H(q[0]);
        H(q[1]);
        CNOT(q[0], q[2]);
        Z(q[1]);
    })")->getComposites()[0];

    auto opt = xacc::getService<xacc::IRTransformation>("single-qubit-gate-merging");
    opt->apply(f, nullptr);
    std::cout << "HOWDY:\n" << f->toString() << "\n";
    EXPECT_EQ(f->nInstructions(), 5);
}

TEST(GateMergingTester, checkTwoQubitSimple) 
{
    auto c = xacc::getService<xacc::Compiler>("staq");
    // Test circuit: Trotter decompose
    auto f = c->compile(R"(
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1079190000000000) q[1];
h q[1];
h q[0];
rz(-0.1079190000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1074780000000000) q[1];
h q[1];
h q[0];
rz(-0.1074780000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1061580000000000) q[1];
h q[1];
h q[0];
rz(-0.1061580000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1039680000000000) q[1];
h q[1];
h q[0];
rz(-0.1039680000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1009290000000000) q[1];
h q[1];
h q[0];
rz(-0.1009290000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0970620000000000) q[1];
h q[1];
h q[0];
rz(-0.0970620000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0924030000000000) q[1];
h q[1];
h q[0];
rz(-0.0924030000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0869880000000000) q[1];
h q[1];
h q[0];
rz(-0.0869880000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0808620000000000) q[1];
h q[1];
h q[0];
rz(-0.0808620000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0740730000000000) q[1];
h q[1];
h q[0];
rz(-0.0740730000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0666780000000000) q[1];
h q[1];
h q[0];
rz(-0.0666780000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0587400000000000) q[1];
h q[1];
h q[0];
rz(-0.0587400000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0503190000000000) q[1];
h q[1];
h q[0];
rz(-0.0503190000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0414870000000000) q[1];
h q[1];
h q[0];
rz(-0.0414870000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0323160000000000) q[1];
h q[1];
h q[0];
rz(-0.0323160000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0228810000000000) q[1];
h q[1];
h q[0];
rz(-0.0228810000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0132570000000000) q[1];
h q[1];
h q[0];
rz(-0.0132570000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0035250000000000) q[1];
h q[1];
h q[0];
rz(-0.0035250000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0062340000000000) q[1];
h q[1];
h q[0];
rz(0.0062340000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0159450000000000) q[1];
h q[1];
h q[0];
rz(0.0159450000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0255240000000000) q[1];
h q[1];
h q[0];
rz(0.0255240000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0348930000000000) q[1];
h q[1];
h q[0];
rz(0.0348930000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0439770000000000) q[1];
h q[1];
h q[0];
rz(0.0439770000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0527010000000000) q[1];
h q[1];
h q[0];
rz(0.0527010000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0609960000000000) q[1];
h q[1];
h q[0];
rz(0.0609960000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0687900000000000) q[1];
h q[1];
h q[0];
rz(0.0687900000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0760230000000000) q[1];
h q[1];
h q[0];
rz(0.0760230000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0826320000000000) q[1];
h q[1];
h q[0];
rz(0.0826320000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0885660000000000) q[1];
h q[1];
h q[0];
rz(0.0885660000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0937770000000000) q[1];
h q[1];
h q[0];
rz(0.0937770000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0982170000000000) q[1];
h q[1];
h q[0];
rz(0.0982170000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1018560000000000) q[1];
h q[1];
h q[0];
rz(0.1018560000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1046610000000000) q[1];
h q[1];
h q[0];
rz(0.1046610000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1066110000000000) q[1];
h q[1];
h q[0];
rz(0.1066110000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1076880000000000) q[1];
h q[1];
h q[0];
rz(0.1076880000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1078860000000000) q[1];
h q[1];
h q[0];
rz(0.1078860000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1071990000000000) q[1];
h q[1];
h q[0];
rz(0.1071990000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1056360000000000) q[1];
h q[1];
h q[0];
rz(0.1056360000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1032090000000000) q[1];
h q[1];
h q[0];
rz(0.1032090000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0999360000000000) q[1];
h q[1];
h q[0];
rz(0.0999360000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0958470000000000) q[1];
h q[1];
h q[0];
rz(0.0958470000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0909750000000000) q[1];
h q[1];
h q[0];
rz(0.0909750000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0853560000000000) q[1];
h q[1];
h q[0];
rz(0.0853560000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0790410000000000) q[1];
h q[1];
h q[0];
rz(0.0790410000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0720780000000000) q[1];
h q[1];
h q[0];
rz(0.0720780000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0645270000000000) q[1];
h q[1];
h q[0];
rz(0.0645270000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0564450000000000) q[1];
h q[1];
h q[0];
rz(0.0564450000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0479040000000000) q[1];
h q[1];
h q[0];
rz(0.0479040000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0389700000000000) q[1];
h q[1];
h q[0];
rz(0.0389700000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0297180000000000) q[1];
h q[1];
h q[0];
rz(0.0297180000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0202230000000000) q[1];
h q[1];
h q[0];
rz(0.0202230000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0105600000000000) q[1];
h q[1];
h q[0];
rz(0.0105600000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0008130000000000) q[1];
h q[1];
h q[0];
rz(0.0008130000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0089400000000000) q[1];
h q[1];
h q[0];
rz(-0.0089400000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0186210000000000) q[1];
h q[1];
h q[0];
rz(-0.0186210000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0281490000000000) q[1];
h q[1];
h q[0];
rz(-0.0281490000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0374490000000000) q[1];
h q[1];
h q[0];
rz(-0.0374490000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0464400000000000) q[1];
h q[1];
h q[0];
rz(-0.0464400000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0550530000000000) q[1];
h q[1];
h q[0];
rz(-0.0550530000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0632130000000000) q[1];
h q[1];
h q[0];
rz(-0.0632130000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0708570000000000) q[1];
h q[1];
h q[0];
rz(-0.0708570000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0779220000000000) q[1];
h q[1];
h q[0];
rz(-0.0779220000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0843510000000000) q[1];
h q[1];
h q[0];
rz(-0.0843510000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0900870000000000) q[1];
h q[1];
h q[0];
rz(-0.0900870000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0950880000000000) q[1];
h q[1];
h q[0];
rz(-0.0950880000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0993120000000000) q[1];
h q[1];
h q[0];
rz(-0.0993120000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1027200000000000) q[1];
h q[1];
h q[0];
rz(-0.1027200000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1052910000000000) q[1];
h q[1];
h q[0];
rz(-0.1052910000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1069980000000000) q[1];
h q[1];
h q[0];
rz(-0.1069980000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1078320000000000) q[1];
h q[1];
h q[0];
rz(-0.1078320000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1077840000000000) q[1];
h q[1];
h q[0];
rz(-0.1077840000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1068510000000000) q[1];
h q[1];
h q[0];
rz(-0.1068510000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1050480000000000) q[1];
h q[1];
h q[0];
rz(-0.1050480000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.1023840000000000) q[1];
h q[1];
h q[0];
rz(-0.1023840000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0988800000000000) q[1];
h q[1];
h q[0];
rz(-0.0988800000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0945690000000000) q[1];
h q[1];
h q[0];
rz(-0.0945690000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0894870000000000) q[1];
h q[1];
h q[0];
rz(-0.0894870000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0836700000000000) q[1];
h q[1];
h q[0];
rz(-0.0836700000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0771690000000000) q[1];
h q[1];
h q[0];
rz(-0.0771690000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0700350000000000) q[1];
h q[1];
h q[0];
rz(-0.0700350000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0623310000000000) q[1];
h q[1];
h q[0];
rz(-0.0623310000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0541170000000000) q[1];
h q[1];
h q[0];
rz(-0.0541170000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0454590000000000) q[1];
h q[1];
h q[0];
rz(-0.0454590000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0364290000000000) q[1];
h q[1];
h q[0];
rz(-0.0364290000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0271020000000000) q[1];
h q[1];
h q[0];
rz(-0.0271020000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0175530000000000) q[1];
h q[1];
h q[0];
rz(-0.0175530000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(-0.0078600000000000) q[1];
h q[1];
h q[0];
rz(-0.0078600000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0018990000000000) q[1];
h q[1];
h q[0];
rz(0.0018990000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0116400000000000) q[1];
h q[1];
h q[0];
rz(0.0116400000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0212880000000000) q[1];
h q[1];
h q[0];
rz(0.0212880000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0307590000000000) q[1];
h q[1];
h q[0];
rz(0.0307590000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0399810000000000) q[1];
h q[1];
h q[0];
rz(0.0399810000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0488730000000000) q[1];
h q[1];
h q[0];
rz(0.0488730000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0573660000000000) q[1];
h q[1];
h q[0];
rz(0.0573660000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0653910000000000) q[1];
h q[1];
h q[0];
rz(0.0653910000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0728820000000000) q[1];
h q[1];
h q[0];
rz(0.0728820000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0797760000000000) q[1];
h q[1];
h q[0];
rz(0.0797760000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0860160000000000) q[1];
h q[1];
h q[0];
rz(0.0860160000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0915540000000000) q[1];
h q[1];
h q[0];
rz(0.0915540000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.0963420000000000) q[1];
h q[1];
h q[0];
rz(0.0963420000000000) q[0];
h q[0];
CX q[0], q[1];
rz(-0.1079190000000000) q[1];
CX q[0], q[1];
h q[1];
rz(0.1003410000000000) q[1];
h q[1];
h q[0];
rz(0.1003410000000000) q[0];
h q[0];
)")->getComposites()[0];
   
    auto opt = xacc::getService<xacc::IRTransformation>("two-qubit-block-merging");
    const auto nbInstBefore = f->nInstructions();
    auto gateRegistry = xacc::getService<xacc::IRProvider>("quantum");
    auto circuitCopy = gateRegistry->createComposite("__COPY__");
    for (size_t i = 0; i < f->nInstructions(); ++i)
    {
        circuitCopy->addInstruction(f->getInstruction(i)->clone());
    }
    
    const auto calculateUMat = [](std::shared_ptr<CompositeInstruction> in_comp){
        // Validate:
        using GateMatrix = Eigen::Matrix2cd;
        const auto H = []() {
        GateMatrix result;
        result << 1.0 / std::sqrt(2), 1.0 / std::sqrt(2), 1.0 / std::sqrt(2),
            -1.0 / std::sqrt(2);
        return result;
        };
        const auto Rx = [](double angle) {
        constexpr std::complex<double> I{0.0, 1.0};

        GateMatrix result;
        result << std::cos(angle / 2.0), -I * std::sin(angle / 2.0),
            -I * std::sin(angle / 2.0), std::cos(angle / 2.0);
        return result;
        };
        const auto Ry = [](double angle) {
        GateMatrix result;
        result << std::cos(angle / 2), -std::sin(angle / 2), std::sin(angle / 2),
            std::cos(angle / 2);
        return result;
        };
        const auto Rz = [](double angle) {
        constexpr std::complex<double> I{0.0, 1.0};

        GateMatrix result;
        result << std::exp(-I * angle / 2.0), 0, 0, std::exp(I * angle / 2.0);
        return result;
        };
        const auto CX = []() {
        Eigen::Matrix4cd cx;
        cx << 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0;
        return cx;
        };
        const auto CZ = []() {
        Eigen::Matrix4cd cx;
        cx << 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1;
        return cx;
        };
        Eigen::Matrix2cd IdMat = Eigen::Matrix2cd::Identity();
        Eigen::Matrix4cd totalU = Eigen::Matrix4cd::Identity();
        for (size_t i = 0; i < in_comp->nInstructions(); ++i)
        {
            auto inst = in_comp->getInstruction(i);
            auto name = inst->name();
            if (name == "CNOT") {
                totalU = CX() * totalU;
            }
            if (name == "CZ") {
                totalU = CZ() * totalU;
            }
            else if (name == "H") {
                if (inst->bits()[0] == 1) {
                    totalU = Eigen::kroneckerProduct(IdMat, H()) * totalU;
                }
                if (inst->bits()[0] == 0) {
                    totalU = Eigen::kroneckerProduct(H(), IdMat) * totalU;
                }
            }
            else if (name == "Rx") {
                const double angle = InstructionParameterToDouble(inst->getParameter(0));
                if (inst->bits()[0] == 1) {
                    totalU = Eigen::kroneckerProduct(IdMat, Rx(angle)) * totalU;
                }
                if (inst->bits()[0] == 0) {
                    totalU = Eigen::kroneckerProduct(Rx(angle), IdMat) * totalU;
                }
            }
            else if (name == "Ry") {
                const double angle = InstructionParameterToDouble(inst->getParameter(0));
                if (inst->bits()[0] == 1) {
                    totalU = Eigen::kroneckerProduct(IdMat, Ry(angle)) * totalU;
                }
                if (inst->bits()[0] == 0) {
                    totalU = Eigen::kroneckerProduct(Ry(angle), IdMat) * totalU;
                }
            }
            else if (name == "Rz") {
                const double angle = InstructionParameterToDouble(inst->getParameter(0));
                if (inst->bits()[0] == 1) {
                    totalU = Eigen::kroneckerProduct(IdMat, Rz(angle)) * totalU;
                }
                if (inst->bits()[0] == 0) {
                    totalU = Eigen::kroneckerProduct(Rz(angle), IdMat) * totalU;
                }
            }
        }

        return totalU;
    }; 
    
    const Eigen::MatrixXcd uMatBeforeLinAlg = calculateUMat(f);
    std::cout << "Total U Before: \n" << uMatBeforeLinAlg << "\n";
    opt->apply(f, nullptr);
    const Eigen::MatrixXcd uMatAfterLinAlg = calculateUMat(f);
    std::cout << "Total U After: \n" << uMatAfterLinAlg << "\n";
    std::cout << "HOWDY:\n" << f->toString() << "\n";

    const auto nbInstAfter = f->nInstructions();
    EXPECT_TRUE(nbInstAfter < nbInstBefore); 
    // Validate using gate fusion:
    const Eigen::MatrixXcd uMatOriginal = GateFuser::fuseGates(circuitCopy, 2);
    const Eigen::MatrixXcd uMatAfter = GateFuser::fuseGates(f, 2);
    std::cout << "Before:\n" << uMatOriginal << "\n";
    std::cout << "After:\n" << uMatAfter << "\n";
    
    const auto compareMatToGlobalPhase = [](const Eigen::MatrixXcd& in_a, const Eigen::MatrixXcd& in_b){
        // Compensate any global phase differences.
        // Find index of the largest element:
        size_t colIdx = 0;
        size_t rowIdx = 0;
        double maxVal = std::abs(in_b(0,0));
        for (size_t i = 0; i < in_b.rows(); ++i)
        {
            for (size_t j = 0; j < in_b.cols(); ++j)
            {
                if (std::abs(in_b(i,j)) > maxVal)
                {
                    maxVal = std::abs(in_b(i,j));
                    colIdx = j;
                    rowIdx = i;
                }
            }
        }

        const std::complex<double> globalFactor = in_a(rowIdx, colIdx) / in_b(rowIdx, colIdx);
        auto uMatAfterFixedPhase = globalFactor * in_b;
        for (size_t i = 0; i < in_b.rows(); ++i)
        {
            for (size_t j = 0; j < in_b.cols(); ++j)
            {
                EXPECT_NEAR(uMatAfterFixedPhase(i, j).real(), in_a(i, j).real(), 1e-9);
                EXPECT_NEAR(uMatAfterFixedPhase(i, j).imag(), in_a(i, j).imag(), 1e-9);
            }
        }
    };

    compareMatToGlobalPhase(uMatOriginal, uMatAfter);
    compareMatToGlobalPhase(uMatBeforeLinAlg, uMatAfterLinAlg);
    compareMatToGlobalPhase(uMatBeforeLinAlg, uMatOriginal);
}

int main(int argc, char **argv) {
  xacc::Initialize(argc, argv);
  ::testing::InitGoogleTest(&argc, argv);
  auto ret = RUN_ALL_TESTS();
  xacc::Finalize();
  return ret;
}
